namespace SimpleWebServices.Web.Mvc
{
    using System;
    using System.Diagnostics.CodeAnalysis;
    using System.Reflection;
    using System.Web.Mvc;
    using System.Web.Routing;

    [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
    public sealed class ResourceAttribute : Attribute
    {
        private const string _languagePattern = @"[a-z]{2}";
        private string _path;

        public ResourceAttribute()
            : this(string.Empty)
        {
        }

        public ResourceAttribute(string path)
        {
            this.Path = path;
        }

        public string Path
        {
            get
            {
                return this._path;
            }

            private set
            {
                if (null == value)
                {
                    throw new ArgumentNullException("value");
                }
                else if (value.StartsWith("/", StringComparison.OrdinalIgnoreCase))
                {
                    throw new FormatException("Path should not start with /");
                }

                this._path = value;
            }
        }

        public void RegisterContentNegotiation(RouteCollection routes, Type controller)
        {
            if (null == routes)
            {
                throw new ArgumentNullException("routes");
            }
            else if (null == controller)
            {
                throw new ArgumentNullException("controller");
            }
            else if (!controller.IsSubclassOf(typeof(Controller)))
            {
                throw new ArgumentOutOfRangeException("controller");
            }
            else if (!controller.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase))
            {
                throw new FormatException("The class name should have \"Controller\" appended.");
            }

            string path = string.Concat(this.Path, ".{language}");

            routes.MapRoute(
                "GET " + path,
                path,
                new { controller = controller.Name.Replace("Controller", string.Empty), action = "ContentNegotiation" },
                new { language = _languagePattern, method = new HttpMethodConstraint("GET") });

            routes.MapRoute(
                "HEAD " + path,
                path,
                new { controller = controller.Name.Replace("Controller", string.Empty), action = "ContentNegotiation" },
                new { language = _languagePattern, method = new HttpMethodConstraint("HEAD") });
        }

        public void RegisterLanguageNegotiation(RouteCollection routes, Type controller)
        {
            if (null == routes)
            {
                throw new ArgumentNullException("routes");
            }
            else if (null == controller)
            {
                throw new ArgumentNullException("controller");
            }
            else if (!controller.IsSubclassOf(typeof(Controller)))
            {
                throw new ArgumentOutOfRangeException("controller");
            }
            else if (!controller.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase))
            {
                throw new FormatException("The class name should have \"Controller\" appended.");
            }

            routes.MapRoute(
                "GET " + this.Path,
                this.Path,
                new { controller = controller.Name.Replace("Controller", string.Empty), action = "LanguageNegotiation" },
                new { method = new HttpMethodConstraint("GET") });

            routes.MapRoute(
                "HEAD " + this.Path,
                this.Path,
                new { controller = controller.Name.Replace("Controller", string.Empty), action = "LanguageNegotiation" },
                new { method = new HttpMethodConstraint("HEAD") });
        }

        [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "MethodInfo is used for type safety.")]
        public void RegisterPost(RouteCollection routes, Type controller, MethodInfo method)
        {
            this.RegisterMethod(routes, controller, method, "POST");
        }

        [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "MethodInfo is used for type safety.")]
        public void RegisterPut(RouteCollection routes, Type controller, MethodInfo method)
        {
            this.RegisterMethod(routes, controller, method, "PUT");
        }

        [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "MethodInfo is used for type safety.")]
        public void RegisterRepresentation(RouteCollection routes, Type controller, MethodInfo method, ContentNegotiationAttribute conneg)
        {
            if (null == routes)
            {
                throw new ArgumentNullException("routes");
            }
            else if (null == controller)
            {
                throw new ArgumentNullException("controller");
            }
            else if (!controller.IsSubclassOf(typeof(Controller)))
            {
                throw new ArgumentOutOfRangeException("controller");
            }
            else if (!controller.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase))
            {
                throw new FormatException("The class name should have \"Controller\" appended.");
            }
            else if (null == method)
            {
                throw new ArgumentNullException("method");
            }
            else if (null == conneg)
            {
                throw new ArgumentNullException("conneg");
            }

            string path = string.Concat(this.Path, ".{language}.", conneg.Extension);

            routes.MapRoute(
                "GET " + path,
                path,
                new { controller = controller.Name.Replace("Controller", string.Empty), action = method.Name },
                new { language = _languagePattern, method = new HttpMethodConstraint("GET") });

            routes.MapRoute(
                "HEAD " + path,
                path,
                new { controller = controller.Name.Replace("Controller", string.Empty), action = method.Name },
                new { language = _languagePattern, method = new HttpMethodConstraint("HEAD") });
        }

        [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "MethodInfo is used for type safety.")]
        private void RegisterMethod(RouteCollection routes, Type controller, MethodInfo method, string value)
        {
            if (null == routes)
            {
                throw new ArgumentNullException("routes");
            }
            else if (null == controller)
            {
                throw new ArgumentNullException("controller");
            }
            else if (!controller.IsSubclassOf(typeof(Controller)))
            {
                throw new ArgumentOutOfRangeException("controller");
            }
            else if (!controller.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase))
            {
                throw new FormatException("The class name should have \"Controller\" appended.");
            }
            else if (null == method)
            {
                throw new ArgumentNullException("method");
            }

            routes.MapRoute(
                string.Concat(value, " ", this.Path),
                this.Path,
                new { controller = controller.Name.Replace("Controller", string.Empty), action = method.Name },
                new { method = new HttpMethodConstraint(value) });
        }
    }
}